#include "serial.h"

struct lt_serial_cfg_s serial_default_cfg = {
	.buad_rate   = 115200,
	.data_bits  = DATA_BITS_8,
	.stop_bits  = STOP_BITS_1,
	.parity     = PARITY_NONE,
	.bit_order  = BIT_ORDER_LSB,
	.invert     = NRZ_NORMAL,
	.bufsz      = 0,
	.reserved   = 0,
};


void serial_buffer_init(struct serial_buffer_s *buffer)
{
	buffer->head = 0;
	buffer->tail = 0;
}

int32_t serial_buffer_put(struct serial_buffer_s *buffer,unsigned char *ch,int32_t size)
{
	int32_t i;
	for(i = 0;i < size;i++){
		buffer->buffer[buffer->head % SERIAL_BUFFER_LENGTH] = ch[i];
		buffer->head++;
	}
    
    /* discard oldest data when bufferd new data is large than buffer size */
    if(buffer->head - buffer->tail >= SERIAL_BUFFER_LENGTH){
            buffer->tail = buffer->head - SERIAL_BUFFER_LENGTH;
    }
    
	if(buffer->head > SERIAL_BUFFER_LENGTH * 10000){
		buffer->head -= SERIAL_BUFFER_LENGTH * 10000;
		buffer->tail -= SERIAL_BUFFER_LENGTH * 10000;
	}
	return i;
}

int32_t serial_buffer_get(struct serial_buffer_s *buffer,unsigned char *ch,int32_t size)
{
	int32_t i;
	int32_t buffer_size = buffer->head - buffer->tail;
	int32_t get_size;
	
	if(buffer_size < 0){
		buffer->head = 0;
		buffer->tail = 0;
		return -1;
	}
	
	get_size = buffer_size > size ? size : buffer_size;
	
	for(i = 0;i < get_size;i++){
		ch[i] = buffer->buffer[buffer->tail % SERIAL_BUFFER_LENGTH];
		buffer->tail++;
	}
	return get_size;
}

int32_t serial_buffer_length(struct serial_buffer_s *buffer)
{
	return buffer->head - buffer->tail;
}

static int32_t serial_init( struct lt_dev_s *dev)
{
	int32_t ret;
	struct lt_serial_s *serial = (struct lt_serial_s *)dev;
	
	if(serial == NULL)
		return -1;
	
	serial_buffer_init(&serial->recv_buffer);
	serial_buffer_init(&serial->send_buffer);
	
	if(serial->init != NULL){
		ret = serial->init(serial);
	}

	if(ret == 0){
		if(serial->configure != NULL){
			serial->configure(serial,&serial_default_cfg);
		}
		serial->tx_done = true;
	}
	return ret;
}

static int32_t serial_deinit( struct lt_dev_s *dev)
{
	int32_t ret;
	struct lt_serial_s *serial = (struct lt_serial_s *)dev;
	
	if(serial == NULL)
		return -1;

	if(serial->disable_irq != NULL){
		serial->disable_irq(serial,SERIAL_INT_RXDONE);
		serial->disable_irq(serial,SERIAL_INT_TXDONE);
		serial->disable_irq(serial,SERIAL_INT_DMATXDONE);
		serial->disable_irq(serial,SERIAL_INT_DMARXDONE);
		serial->disable_irq(serial,SERIAL_INT_IDLE);
	}
	
	if(dev->open_flag | LT_DEVICE_FLAG_DMA_TX){
		if(serial->dma_tx_deinit != NULL){
			serial->dma_tx_deinit(serial);
		}
	}

	if(dev->open_flag | LT_DEVICE_FLAG_DMA_RX){
		if(serial->dma_rx_deinit != NULL){
			serial->dma_rx_deinit(serial);
		}
	}

	if(serial->deinit != NULL){
		ret = serial->deinit(serial);
	}

	return ret;
}

static int32_t serial_open( struct lt_dev_s *dev, uint16_t oflag)
{
	int32_t ret = 0;
	struct lt_serial_s *serial = (struct lt_serial_s *)dev;
	
	if(serial == NULL){
		return -1;
    }

	if(serial->disable_irq != NULL){
		serial->disable_irq(serial,SERIAL_INT_RXDONE);
		serial->disable_irq(serial,SERIAL_INT_TXDONE);
		serial->disable_irq(serial,SERIAL_INT_DMATXDONE);
		serial->disable_irq(serial,SERIAL_INT_DMARXDONE);
		serial->disable_irq(serial,SERIAL_INT_IDLE);
	}
	
    if(oflag & LT_DEVICE_FLAG_INT_TX){ 
        if(!(dev->flag & LT_DEVICE_FLAG_INT_TX)){
            ret = -1;
        }else{
			dev->open_flag |= LT_DEVICE_FLAG_INT_TX;
		}
    }

    if(oflag & LT_DEVICE_FLAG_INT_RX){
        if(!(dev->flag & LT_DEVICE_FLAG_INT_RX)){
            ret = -1;
        }else{
            serial->enable_irq(serial,SERIAL_INT_RXDONE);
			dev->open_flag |= LT_DEVICE_FLAG_INT_RX;
        }
    }

    if(oflag & LT_DEVICE_FLAG_DMA_TX){
        if(!(dev->flag & LT_DEVICE_FLAG_DMA_TX)){
            ret = -1;
        }else{
            if(serial->dma_tx_init != NULL){
                serial->dma_tx_init(serial);
                serial->enable_irq(serial,SERIAL_INT_DMATXDONE);
                serial->enable_irq(serial,SERIAL_INT_TXDONE);
				dev->open_flag |= LT_DEVICE_FLAG_DMA_TX;
            }
            
        }
    }

    if(oflag & LT_DEVICE_FLAG_DMA_RX) {
        if(!(dev->flag & LT_DEVICE_FLAG_DMA_RX)){
            ret = -1;
        }else{
            if(serial->dma_rx_init != NULL){
                serial->dma_rx_init(serial);
                serial->enable_irq(serial,SERIAL_INT_DMARXDONE);
				dev->open_flag |= LT_DEVICE_FLAG_DMA_RX;
            }
        }
    }

	return ret;
}

static int32_t serial_close(struct lt_dev_s *dev)
{
	int32_t ret = -1;
	struct lt_serial_s *serial = (struct lt_serial_s *)dev;
	
	if(serial == NULL)
		return -1;
	
    dev->open_flag = 0x00;
    ret = 0;
	return ret;
}

static int32_t serial_read(struct lt_dev_s *dev, void *buffer, int32_t buflen,int32_t pos)
{   
	int32_t ret = -1;
    int32_t i,j;
    int32_t length;
    int32_t dma_write_ptr;
	struct lt_serial_s *serial = (struct lt_serial_s *)dev;
	unsigned char *chbuf = (unsigned char*)buffer;
	
	if(serial == NULL)
		return -1;

    if(serial->dev.open_flag & LT_DEVICE_FLAG_DMA_RX){
        dma_write_ptr = serial->dma_rx_get_data_length(serial);
        length = dma_write_ptr - serial->dma_read_ptr;
        
        if(length < 0){
            length += SERIAL_DMA_RX_BUFFER_LENGTH;
        }
        
        if(length < 0){
            length = 0;
            serial->dma_read_ptr = dma_write_ptr;
        }
        
        length = length > buflen ? buflen : length;
        
        for(i = 0, j = serial->dma_read_ptr;i < length; i++,j++){
            if(j >= SERIAL_DMA_RX_BUFFER_LENGTH){
                j = 0;
            }
            chbuf[i] = serial->dma_rx_buffer[j];
        }
        
        serial->dma_read_ptr += length;
        
        if(serial->dma_read_ptr >= SERIAL_DMA_RX_BUFFER_LENGTH){
            serial->dma_read_ptr -= SERIAL_DMA_RX_BUFFER_LENGTH;
        }
        ret = length;
    }else if(serial->dev.open_flag & LT_DEVICE_FLAG_INT_RX){
        ret = serial_buffer_get(&serial->recv_buffer,chbuf,buflen);
    }
    
	return ret;
}

static int32_t serial_write(struct lt_dev_s *dev, const void *buffer, int32_t buflen,int32_t pos)
{   
	int32_t ret = -1;
	struct lt_serial_s *serial = (struct lt_serial_s *)dev;
	unsigned char *chbuf = (unsigned char*)buffer;
	unsigned char ch;
    int32_t dma_size;
    
	if(serial == NULL)
		return -1;
	
    if(serial->dev.open_flag & LT_DEVICE_FLAG_DMA_TX){
        if(serial->dma_tx_start != NULL){
            while(serial->tx_done == false){
			}
			serial->tx_done = false;
			serial_buffer_put(&serial->send_buffer,chbuf,buflen);
			dma_size = buflen > SERIAL_DMA_TX_BUFFER_LENGTH ? SERIAL_DMA_TX_BUFFER_LENGTH : buflen;
			serial_buffer_get(&serial->send_buffer,serial->dma_tx_buffer,dma_size);
			serial->dma_tx_start(serial,dma_size);
			ret = buflen;
        }
    }else if(serial->dev.open_flag & LT_DEVICE_FLAG_INT_TX){
        while(serial->tx_done == false){
		}
		serial->tx_done = false;
		serial_buffer_put(&serial->send_buffer,chbuf,buflen);
		serial->enable_irq(serial,SERIAL_INT_TXDONE);
		serial_buffer_get(&serial->send_buffer,&ch,1);
		ret = serial->putc(serial,ch);
    }

	return ret;
}

static int32_t serial_ioctl(struct lt_dev_s *dev,uint8_t cmd,void *arg)
{
	struct lt_serial_s *serial = (struct lt_serial_s *)dev;
	struct lt_serial_cfg_s *cfg;
	int32_t ret = -1;
	
	if(serial == NULL){
		return ret;
    }
	
	switch(cmd){
		case LT_DEVICE_CTRL_CONFIG:
			
            cfg = (struct lt_serial_cfg_s *)arg;
            if(cfg == NULL)
                ret = -1;
            if(serial->configure != NULL){
                ret = serial->configure(serial,cfg);
            }else{
                ret = -1;
            }
            
			break;
        case LT_DEVICE_CTRL_FIFOFLUSH:
			
            serial->recv_buffer.tail = serial->recv_buffer.head;
            serial->send_buffer.tail = serial->send_buffer.head;
            
			break;
		default:
			break;
	}
	return ret;
}

void serial_isr(struct lt_serial_s *serial,int32_t event)
{
	unsigned char ch;
	int32_t len;
	
	switch(event){
		case SERIAL_INT_TXDONE:
			if(serial_buffer_get(&serial->send_buffer,&ch,1) == 1){
				serial->putc(serial,ch);
			}else{
				/* all bytes in buffer are send by TC interrupt */
                serial->disable_irq(serial,SERIAL_INT_TXDONE);
				serial->tx_done = true;
			}
			break;
		case SERIAL_INT_RXDONE:
			serial->getc(serial,&ch);
			serial_buffer_put(&serial->recv_buffer,&ch,1);
			break;
		case SERIAL_INT_DMATXDONE:
            len = serial_buffer_get(&serial->send_buffer,serial->dma_tx_buffer,SERIAL_DMA_TX_BUFFER_LENGTH);
            if(len > 0){
                serial->dma_tx_start(serial,len);
            }else{
                serial->tx_done = true;
            }
			
			break;
        case SERIAL_INT_DMARXDONE:
			/* nothing need to do */
			break;
		default:
			break;
	};
}

void serial_device_register(struct lt_serial_s *serial,char *name,uint16_t flag)
{
	serial->dev.type   = LT_Device_Class_Char;
	serial->dev.init   = serial_init;
	serial->dev.deinit = serial_deinit;
	serial->dev.open   = serial_open;
	serial->dev.close  = serial_close;
	serial->dev.read   = serial_read;
	serial->dev.write  = serial_write;
	serial->dev.ioctl  = serial_ioctl;
	
	lt_dev_register(&serial->dev,name,flag);
}

void serial_device_unregister(struct lt_serial_s *serial,char *name,uint16_t flag)
{
	lt_dev_unregister(&serial->dev);
}
